#pragma once
/*
 * CSharePtrSession is a share pointer session to implement ISession which runs 
 * the net events(data,connection,disconnection) in the same thread.
 * This class must be used with CSharePtrSessionFactory and CMainService class together.
 */

#include <functional>
#include "anet.hpp"
#include "connection.hpp"
#include "main_service.h"

namespace anet {
	namespace tcp {
		class IReleaser;

		// CSharePtrSession is a share pointer session which derives from std::enable_shared_from_this and ISession.
		// The template proxySession is a proxy session to execute the ISession's pure interface. that is to say:
		// it must to implement the following template functions:
		// 1 TCP connection builds: OnConnected()
		// 2 Receiving message from the tcp connection: OnMessage(const char *msg, int len);
		// 3 TCP terminate: OnTerminate()
		// 4 object release: OnRelease()
		// 5 class create: Create()

		template <typename proxySession>
		class CSharePtrSession : public ISession, 
		         public std::enable_shared_from_this<CSharePtrSession<proxySession>> {
		public:
			CSharePtrSession() : m_conn(nullptr), m_id(0), m_closed(true),
				m_releaser(nullptr), m_proxySession(nullptr) {
				m_proxySession = proxySession::Create();
				m_proxySession->SetSession(this);
			}
			explicit CSharePtrSession(proxySession* pSession) : m_conn(nullptr), 
				m_id(0), m_closed(true), m_releaser(nullptr), m_proxySession(nullptr) {
				m_proxySession = pSession;
				m_proxySession->SetSession(this);
			}
			virtual ~CSharePtrSession() = default;

		public:
			// ISession interface.
			virtual void onRecv(const char *msg, int len) override {
				auto self = this->shared_from_this();
				std::vector<char> strMsg(msg, msg + len);
				CMainService::instance().post([self, this, strMsg = std::move(strMsg)] {
					this->OnMessage(strMsg.data(), strMsg.size());
				});
			}

			virtual void onConnected(connSharePtr conn) override {
				m_closed = false;
				m_conn = conn;

				auto self = this->shared_from_this();
				CMainService::instance().post([self, this]() {
					this->OnConnected();
				});
			}
			virtual void onTerminate() override {
				m_closed = true;

				auto self = this->shared_from_this();
				CMainService::instance().post([self, this]() {
					this->OnTerminate();
				});
			}
			virtual void release() override {
				auto self = this->shared_from_this();
				CMainService::instance().post([self, this]() {
					this->OnRelease();
					this->Release();
				});
			}

		public:
			// == template interface. ===
			// please do not change them.
			void setReleaser(IReleaser *releaser) {
				assert(releaser != nullptr && "release is nullptr.");
				m_releaser = releaser;
			}

			// session id.
			unsigned int getId() const {
				return m_id;
			}
			void setId(unsigned int id) {
				m_id = id;
			}

			// release interface.
			void Release() {
				if (m_releaser != nullptr) {
					auto self = this->shared_from_this();
					m_releaser->releaseSession((void*)&self);
				}
			}

		public:
			// on tcp connection connected callback.
			void OnConnected() {
				m_proxySession->OnConnected();
			}

			// on receiving a complete message including the protocol head.
			void OnMessage(const char *msg, int len) {
				m_proxySession->OnMessage(msg, len);
			}

			// on tcp connection terminate callback.
			void OnTerminate() {
				m_proxySession->OnTerminate();
			}

			// on tcp connection release callback.
			void OnRelease() {
				m_proxySession->OnRelease();
			}

		public:
			// sending message interface.
			void Send(const std::string& msg) {
				this->Send(msg.c_str(), int(msg.size()));
			}
			void Send(const char *msg, int len) {
				if (msg == nullptr || len == 0) {
					return;
				}
				if (IsConnected()) {
					m_conn->Send(msg, len);
				}
			}

			// whether it is connected
			bool IsConnected() {
				return !m_closed;
			}

			// close the connection.
			void Close() {
				if (IsConnected()) {
					m_conn->close();
				}
			}

			// return remote ip
			std::string getRemoteIP() {
				return m_conn->getRemoteAddr();
			}

			// return remote port.
			unsigned short getRemotePort() {
				return m_conn->getRemotePort();
			}

			// get proxy session.
			proxySession* getProxySession() {
				return m_proxySession;
			}

		protected:
			// tcp connection pointer.
			connSharePtr m_conn{ nullptr };

			// session id
			unsigned int m_id{1};

			// whether it is closed.
			std::atomic_bool m_closed{ true };

			// releaser pointer.
			IReleaser *m_releaser{ nullptr };

			// template session object.
			proxySession* m_proxySession{ nullptr };
		};


		// releaser class to release the share pointer session.
		class IReleaser {
		public:
			virtual ~IReleaser() {}

			// session is a pointer of detail session.
			virtual void releaseSession(void* pSession) = 0;
		};


		// CSharePtrSessionFactory is a share pointer session factory which 
		// creates session from CSharePtrSession<proxySession>.
		template <typename proxySession>
		class CSharePtrSessionFactory : public ISessionFactory, public IReleaser {
		public:
			CSharePtrSessionFactory() : m_nextId(1) {}
			virtual ~CSharePtrSessionFactory() = default;
			CSharePtrSessionFactory(const CSharePtrSessionFactory&) = delete;
			CSharePtrSessionFactory& operator = (const CSharePtrSessionFactory&) = delete;

			// CSharePtrSession session.
			typedef CSharePtrSession<proxySession> sharePtrSession;

			// share pointer session.
			typedef std::shared_ptr<sharePtrSession> sessionSharePtr;
			typedef std::map<unsigned int, sessionSharePtr> Id2SessionPtr;

		public:
			// ISessionFactory interface.
			virtual ISession* createSession() override {
				return _createSession(nullptr);
			}

			// new create session with proxySession pointer interface.
			ISession* createSession(proxySession* pSession) {
				return _createSession(pSession);
			}

			// IReleaser interface.
			virtual void releaseSession(void* session) override {
				if (session == nullptr) {
					return;
				}

				// remove from the map.
				auto &realSession = *(sessionSharePtr*)session;
				std::lock_guard<std::mutex> mu(m_lock);
				m_sessions.erase(realSession->getId());
			}

			// find interface.
			std::pair<bool, sessionSharePtr> findSession(unsigned int id) const {
				std::lock_guard<std::mutex> mu(m_lock);
				auto it = m_sessions.find(id);
				if (it != m_sessions.end()) {
					return { true,it->second };
				} else {
					return { false,sessionSharePtr{} };
				}
			}

		protected:
			ISession* _createSession(proxySession* pSession) {
				std::lock_guard<std::mutex> mu(m_lock);

				// create session's share pointer.
				unsigned int sessionId = m_nextId++;

				// session share pointer.
				std::shared_ptr<sharePtrSession> sessSharePtr;
				if (pSession != nullptr) {
					sessSharePtr = std::make_shared<sharePtrSession>(pSession);
				} else {
					sessSharePtr = std::make_shared<sharePtrSession>();
				}

				// set id.
				sessSharePtr->setId(sessionId);

				// set releaser.
				sessSharePtr->setReleaser(this);

				// save it to map to keep the life.
				m_sessions[sessSharePtr->getId()] = sessSharePtr;
				return sessSharePtr.get();
			}

		protected:
			// The lock is for sessions and next id.
			mutable std::mutex m_lock;
			Id2SessionPtr m_sessions;
			unsigned int m_nextId{ 1 };
		};
	}
}
